#include <string>
#include <vector>
#include "LevelMap.h"
#include "Level.h"

#include "MyGame.h"
#include "Factory.h"
#include "SoundCache.h"
#include "MusicCache.h"
#include "SurfaceCache.h"
#include "Physics.h"
#include "Camera.h"
#include "NumberUtility.h"

LevelMap::LevelMap()
{
    this->type = stLevelMap;
    this->init();
    this->fileName = "UntitledMap.map";
    this->decryptor = NULL;
};

LevelMap::LevelMap(string password)
{
    this->type = stLevelMap;
    this->init();
#if _DEBUG
    cout << password << endl;
#endif
    this->decryptor = new Decryptor(password.c_str());
};

LevelMap::LevelMap(string fileName, string password)
{
    this->type = stLevelMap;
    this->init();
    this->decryptor = new Decryptor(password.c_str());
    this->LoadLevelMap(fileName);
//	this->Activate();
};

LevelMap::~LevelMap()
{
    if (this->decryptor)
    {
        delete this->decryptor;
        this->decryptor = NULL;
    }
    vector< Overlay* >::iterator I;
    for (I = overlays.begin(); I < overlays.end(); ++I)
    {
        delete (*I);
    }
    overlays.clear();
    vector< BackgroundImage* >::iterator L;
    for (L = backgroundImages.begin(); L < backgroundImages.end(); ++L)
    {
        delete (*L);
    }
    backgroundImages.clear();
    list< StatefulUnit* >::iterator M;
    for (M = effects.begin(); M != effects.end(); ++M)
    {
        delete (*M);
    }
    effects.clear();
    vector< Sequence* >::iterator S;
    for (S = sequences.begin(); S < sequences.end(); ++S)
    {
        delete (*S);
    }
    SurfaceCache::GetSurfaceCache()->Clear();
    SoundCache::GetSoundCache()->Clear();
    MusicCache::GetMusicCache()->Clear();

    #ifdef _DEBUG
    cout << "Deleted old level map" << endl;
    #endif
};

/* preload the map to allow us to effectively switch on a string */
void LevelMap::init()
{
    this->sequenceRunning = false;

    #ifdef _DEBUG
    debugPrint.loadFont("font/unispace.ttf",12);
    debugPrint.setPosition(10,10);
    debugPrint.setColour(RED);
    debugPrint.setRelativity(true);

    pix.setColour(YELLOW);
    pix.setAlpha(50);
    #endif
}

/* set, or reset the password for decrypting encrypted level files */
void LevelMap::SetPassword(string password)
{
    if (this->decryptor)
    {
        delete this->decryptor;
    }
    this->decryptor = new Decryptor(password.c_str());
}

/* Read a level file and parse it */
void LevelMap::LoadLevelMap(string fileName)
{
    wstring temp;
    //  Remove windows line endings...
    fileName = StringUtility::stripLineEndings(fileName);
    /* Check if the file exists, if it does not, look for an encrypted file of the same name with .enc appended */
    this->fileName = fileName;
    if (!this->FileExists(fileName))
    {
        fileName += ".enc";
    }
#if _DEBUG
    cout << fileName << endl;
#endif

    /* Either decrypt the file or read it straight, ending with the data in the wstring temp either way */
    if (fileName.length()>4 && fileName.substr(fileName.length()-4,4) == ".enc")
    {
        temp = this->decryptor->decrypt_w((char *)fileName.c_str());
    }
    else
    {
        /* read the entire file, then convert from UTF-8 to wstring */
        string line, temp2;
        ifstream file(fileName.c_str());
        while(std::getline(file, line))
        {
            temp2 += StringUtility::stripLineEndings(line) + "\n";
        }
        file.close();
        temp = this->decryptor->stringToWString(temp2);
    }

#if _DEBUG
//	cout << this->decryptor->wstringToString(temp) << endl << endl << endl;
    //fstream test("test.out");
#endif
    LoadLevelMapFromWString(temp);
}

/* Parse a levelfile from a wstring */
void LevelMap::LoadLevelMapFromWString(wstring temp)
{
    unsigned int currentPos = 0;
    unsigned int nextPos = 0;
    uint i,j,count;
    int k;
    wstring cur;
    string stemp;
    string type;

    getLine(temp, cur, currentPos);
    currentPos = 0;
    stemp = decryptor->wstringToString(cur);
    #ifdef _DEBUG
    cout << "Loading level..." << endl;
    #endif
    if (stemp.substr(0,1) == "#" || stemp.substr(0,1) == "v" || stemp.substr(0,1) == "V")
    {
        LoadLevelMapFromWStringVersioned(temp);
    }
    else
    {
        cout << "Bad level file!" << endl;
        exit(-1);
    }
};

/* Find the next line in a wstring */
bool LevelMap::getLine(wstring temp, wstring& line, uint& currentPos)
{
    int nextPos = temp.find(L"\n", currentPos);
    if (nextPos==-1)
    {
        currentPos=nextPos;
        line = L"";
        return false;
    }
    else
    {
        wstring result = temp.substr(currentPos, (nextPos-currentPos));
        currentPos = nextPos+1;
        line = result;
        return true;
    }
}

/* Parse a levelfile from a wstring, new format */
void LevelMap::LoadLevelMapFromWStringVersioned(wstring temp)
{
    Factory *f = Factory::GetFactory();

    uint currentPos = 0;
    uint nextPos = 0;
    uint i = 0;
    wstring wline;
    string line;
    vector<string> split;
    string field;
    wstring wcur;
    string cur;
    string stemp;

    Vector2di backgroundSize = Vector2di(0,0);
    Vector2di backgroundOffset = Vector2di(0,0);
    Vector2di backgroundMoveA = Vector2di(1,1);
    Vector2di backgroundForEveryB = Vector2di(1,1);
    pair<bool,bool> backgroundRepeat = make_pair(false,false);
    uint backgroundStartIndex = 0;
    uint backgroundNumFrames = 1;
    uint backgroundFrameRate = 0;
    string backgroundFileName;
    uint backgroundSheetWidth;
    uint backgroundSheetHeight;
    AnimatedSprite *backgroundSprite;
    //AnimatedSprite *overlaySprite;
    uint backgroundTransparentColour = -1;
    bool backgroundUseTransparentPoint = false;
    Vector2di backgroundTransparentPoint = Vector2di(0,0);
    BackgroundImage *backgroundImageTemp;
    SDL_Surface *surface;
    SoundCache *sounds = SoundCache::GetSoundCache();
    MusicCache *music = MusicCache::GetMusicCache();
    SurfaceCache *surfaces = SurfaceCache::GetSurfaceCache();
    Overlay *overlay;
    StatefulSprite *overlaySprite;
    Sequence *sequence;

    string version;

    while (getLine(temp, wline, currentPos)==true)
    {
        line = decryptor->wstringToString(wline);
        line = StringUtility::substrReplace(line,"\r","");
        // If it is not a comment line
        if (line.size() > 0 and line.substr(0,1) != "#" and line.at(0) > 13)
        {
            // chop on equal into at most two parts
            tokenize(line,split,"=",2);
            field = split[0];
            if (split.size()<=1)
            {
                //apparently there is nothing after the equal
                //ignore it
            }
            else
            {
                cur = split[1];
                if (field == "Version")
                {
                    version = cur;
                }
                else if (field == "LevelMapName")
                {
                    levelMapName = cur;
                }
                else if (field == "TransparentKey")
                {
                    transparent = atoi(cur.c_str());;
                }
                else if (field == "TransparentPoint")
                {
                }
                // Not sure if we'll need these
                else if (field == "LevelMapWidth")
                {
                    mapWidth = atoi(cur.c_str());
                }
                else if (field == "LevelMapHeight")
                {
                    mapHeight = atoi(cur.c_str());
                }
                else if (field == "LevelMapSize")
                {
                    tokenize(cur,split,",",2);
                    mapWidth = atoi(split[0].c_str());
                    mapHeight = atoi(split[1].c_str());
                }
                else if (field == "Overlay")
                {
                    overlay = f->GetOverlay(this, cur, transparent);
                    this->overlays.push_back(overlay);
                }
                else if (field == "BackgroundImageWidth")
                {
                    backgroundSize.x = atoi(cur.c_str());
                }
                else if (field == "BackgroundImageHeight")
                {
                    backgroundSize.y = atoi(cur.c_str());
                }
                else if (field == "BackgroundImageSize")
                {
                    tokenize(cur,split,",",2);
                    backgroundSize.x = atoi(split[0].c_str());
                    backgroundSize.y = atoi(split[1].c_str());
                }
                else if (field == "BackgroundImageOffset")
                {
                    tokenize(cur,split,",",2);
                    backgroundOffset.x = atoi(split[0].c_str());
                    backgroundOffset.y = atoi(split[1].c_str());
                }
//                else if (field == "BackgroundImageMoveA")
//                {
//                    tokenize(cur,split,",",2);
//                    backgroundMoveA.x = atoi(split[0].c_str());
//                    backgroundMoveA.y = atoi(split[1].c_str());
//                }
//                else if (field == "BackgroundImageForEveryB")
//                {
//                    tokenize(cur,split,",",2);
//                    backgroundForEveryB.x = atoi(split[0].c_str());
//                    backgroundForEveryB.y = atoi(split[1].c_str());
//                }
//                else if (field == "BackgroundImageTransparentKey")
//                {
//                    backgroundTransparentColour = atoi(split[1].c_str());
//                    backgroundUseTransparentPoint = false;
//                }
//                else if (field == "BackgroundImageTransparentPoint")
//                {
//                    tokenize(cur,split,",",2);
//                    backgroundTransparentPoint = Vector2di(atoi(split[0].c_str()),atoi(split[1].c_str()));
//                    backgroundUseTransparentPoint = true;
//                }
                else if (field == "BackgroundImage")
                {
                    tokenize(cur,split,",",6);
                    if (split.size() > 3)
                    {
                        backgroundFrameRate = atoi(split[1].c_str());
                        backgroundStartIndex = atoi(split[2].c_str());
                        backgroundNumFrames = atoi(split[3].c_str());
                    }
                    else
                    {
                        backgroundStartIndex = 0;
                        backgroundNumFrames = 1;
                        backgroundFrameRate = 0;
                    }
                    backgroundFileName = split[0];
                    bool dontNeedReturn;
                    surface = surfaces->GetSurface(backgroundFileName,dontNeedReturn);
                    if (!surface)
                    {
                        cout << "Critical error: reading " << backgroundFileName << " failed!" << endl;
                        exit(-1);
                    }

                    // Figure out the number of xTiles and yTiles
                    backgroundSheetWidth = surface->w/backgroundSize.x;
                    backgroundSheetHeight = surface->h/backgroundSize.y;

                    backgroundSprite = new AnimatedSprite();
                    backgroundSprite->loadFrames(surface,backgroundSheetWidth,backgroundSheetHeight,backgroundStartIndex,backgroundNumFrames);
                    if (backgroundUseTransparentPoint)
                    {
                        backgroundSprite->setTransparentColour(backgroundTransparentPoint);
                    }
                    else
                    {
                        if (backgroundTransparentColour != 0)
                        {
                            backgroundSprite->setTransparentColour(Colour(backgroundTransparentColour));
                        }
                    }

                    if (backgroundFrameRate > 0)
                    {
                        backgroundSprite->setFrameRate((TimerScalers)backgroundFrameRate);
                    }

                    backgroundImageTemp = new BackgroundImage(backgroundSprite, backgroundOffset, backgroundMoveA, backgroundForEveryB,backgroundRepeat);
                    this->backgroundImages.push_back(backgroundImageTemp);
                }
                else if (field == "Sound")
                {
                    tokenize(cur,split,",",3);

                    if (split.size() == 3)
                    {
                        sounds->AddSound(split[0],split[1],(bool)atoi(split[2].c_str()));
                    }
                    else if (split.size() == 2)
                    {
                        if (split[2].length() > 1)
                            sounds->AddSound(split[0],split[1],false);
                        else
                            sounds->AddSound(split[0],(bool)atoi(split[2].c_str()));
                    }
                    else if (split.size() == 1)
                    {
                        sounds->AddSound(split[0],false);
                    }
                }
                else if (field == "Music")
                {
                    tokenize(cur,split,",",2);
                    if (split.size() == 2)
                    {
                        music->AddMusic(split[0],split[1]);
                    }
                    else if (split.size() == 1)
                    {
                        music->AddMusic(split[0]);
                    }
                }
                else if (field == "BackgroundMusic")
                {
                    music->PlayMusic(cur);
                }
                else if (field == "Sequence")
                {
                    sequence = new Sequence(cur);
                    sequence->setLevel(this);
                    sequences.push_back(sequence);
                }
                else if (field == "Step" && !(sequence == NULL))
                {
                    sequence->addStep(cur);
                }
                else if (field == "InitialSequence")
                {
                    initialSequence = cur; //StartSequence(cur);
                }
            }
        }
    }
}

/* Utility function to test for file existence */
//TODO: move utility functions to singleton utility class
bool LevelMap::FileExists(string filename)
{
    struct stat stFileInfo;
    bool blnReturn;
    int intStat;

    // Attempt to get the file attributes
    intStat = stat(filename.c_str(),&stFileInfo);
    if(intStat == 0)
    {
        // We were able to get the file attributes
        // so the file obviously exists.
        blnReturn = true;
    }
    else
    {
        // We were not able to get the file attributes.
        // This may mean that we don't have permission to
        // access the folder which contains this file.
        blnReturn = false;
    }

    return(blnReturn);
}

Vector2di LevelMap::GetCenterPosition(Vector2df Pos, Vector2df Size)
{
    return Vector2di(Pos.x+Size.x/2,Pos.y+Size.y/2);
}

void LevelMap::Activate()
{
    nullify = false;

    CAMERA->jumpTo(Vector2df(0.0,0.0));

    GFX::setClearColour(BLACK);

    MyGame* game = MyGame::GetMyGame();
    game->SetCurrentLevelMapName(levelMapName);

    // Gather current state of overlays
    GameState *gs = game->GetGameState();
    GameLevelState *gls = NULL;
    GameLevelState *lastAvailable = NULL;
    bool oneAvailable = false;
    bool oneFocused = false;
    vector< Overlay* >::iterator I;
    string target;
    for (I = overlays.begin(); I < overlays.end(); ++I) {
        gls = gs->GetGameLevelState(levelMapName, (*I)->getIdentifier());
        if (gls == NULL) {
            (*I)->setSpriteState("Unavailable");
        } else {
            (*I)->setSpriteState(gls->GetState());
            if ((*I)->getSpriteState() == "Available" || (*I)->getSpriteState() == "Focused" || (*I)->getSpriteState() == "Completed" ) {
                oneAvailable = true;
                lastAvailable = gls;
                if ((*I)->getSpriteState() == "Focused") oneFocused = true;
            }
            if (gls->GetSeenByLevelMap() == false) {
                gls->SetSeenByLevelMap(true);
                string sequenceName = (*I)->getIdentifier() + ":" + (*I)->getSpriteState();
                StartSequence(sequenceName);
            }
        }
    }

    if (!(lastAvailable == NULL) && oneFocused == false) {
        SetState(lastAvailable->GetLevelName(), "Focused");
    }
    if ((!oneAvailable) && !initialSequence.empty()) {
        StartSequence(initialSequence);
    }
}

void LevelMap::ReActivate()
{
    Activate();
}

void LevelMap::userInput()
{
    input->update();

#ifdef PLATFORM_PC
    if(input->isQuit())
        nullifyState();
#endif

    vector< Overlay* >::iterator I;
    Overlay *current;
    string target;
    for (I = overlays.begin(); I < overlays.end(); ++I) {
        if ((*I)->getSpriteState() == "Focused") {
            current = (*I);
            target = (*I)->getTarget();
        }
    }

    if(input->isA())
    {
        if (!target.empty())
            GoToLevel("Level," + target);
    }
    if (!(current == NULL)) {
        if(input->isLeft())
        {
            StartSequence(current->getIdentifier() + ":" + "Left");
        }
        if(input->isRight())
        {
            StartSequence(current->getIdentifier() + ":" + "Right");
        }
        if(input->isUp())
        {
            StartSequence(current->getIdentifier() + ":" + "Up");
        }
        if(input->isDown())
        {
            StartSequence(current->getIdentifier() + ":" + "Down");
        }
    }
    input->resetKeys();
}

void LevelMap::pauseInput()
{
    input->update();

//#ifdef PLATFORM_PC
//    if(input->isQuit())
//        nullifyState();
//#endif
//
//    if (isLogicPaused)
//    {
//        //
//    }
//    else
//    {
//        if(input->isStart())
//        {
//            pauseToggle();
//        }
//        if (input->isUp())
//            pauseMenu.menuUp();
//        else if (input->isDown())
//            pauseMenu.menuDown();
//        if (input->isA())
//        {
//            switch (pauseMenu.getSelection())
//            {
//            case 0: // Resume
//                pauseToggle();
//                break;
//            case 1: // quit
//                setNextState(STATE_MAIN);
//                break;
//            }
//        }
//        input->resetKeys();
//    }
}

#ifdef PENJIN_SDL
void LevelMap::render(SDL_Surface *screen)
{

    vector<vector< BaseUnit* >::iterator>::iterator spriteIter;
    vector<vector< BaseUnit* >::iterator> spritesToBeRemoved;
    //cout << "Rendering...";
    GFX::clearScreen(screen);

    for (uint I = 0; I < backgroundImages.size(); I++)
    {
        BackgroundImage *img;
        img = backgroundImages[I];
        img->updateScreenPosition(CAMERA->getMapOffset());
        img->render();
    }

#ifdef _DEBUG
    pix.setColour(MAGENTA);
    pix.setAlpha(255);
#endif

    vector< Overlay* >::iterator I;
    for (I = overlays.begin(); I < overlays.end(); ++I)
    {
        (*I)->updateScreenPosition(CAMERA->getMapOffset());
        (*I)->render();
    }

    StatefulUnit *effect;
    list<StatefulUnit *>::iterator iter;
    list<StatefulUnit *> effectsToBeRemoved;
    for (iter = effects.begin(); iter != effects.end(); iter++)
    {
        effect = *iter;
        effect->updateScreenPosition(CAMERA->getMapOffset());
        if (effect->hasFinished())
        {
            effectsToBeRemoved.push_back(effect);
        }
        else
        {
            effect->render();
        }
    }

    for (iter = effectsToBeRemoved.begin(); iter != effectsToBeRemoved.end(); iter++)
    {
        effects.remove(*iter);
    }

#ifdef _DEBUG
    pix.setColour(YELLOW);
    pix.setAlpha(50);

#endif
}
#else
void LevelMap::render()
{
    GFX::clearScreen();
}
#endif
void LevelMap::update()
{
    CAMERA->update();

    vector< Overlay* >::iterator I;

    for (I = overlays.begin(); I < overlays.end(); ++I)
    {
        (*I)->update();
    }

    vector<Sequence *>::iterator seq;

    for (seq = sequences.begin(); seq < sequences.end(); ++seq)
    {
        (*seq)->update();
    }
    //CAMERA->move(GetHero(),GetHero()->getSpeed());

    list<StatefulUnit *>::iterator iter;
    for (iter = effects.begin(); iter != effects.end(); iter++)
    {
        (*iter)->update();
    }
}

/* Utility function - this sets us up for the next level */
void LevelMap::GoToLevel(string levelFileName)
{
    if (levelFileName.find(".") != string::npos)
    {
        MyGame* game = MyGame::GetMyGame();
        game->SetLevelFileName(levelFileName);
        this->setNextState(STATE_LEVEL);
    }
}

void LevelMap::AddOverlay(Overlay *overlay)
{
    overlays.push_back(overlay);
};

void LevelMap::RemoveOverlay(Overlay *overlay)
{
    vector< Overlay* >::iterator I;
    for (I = overlays.begin(); I < overlays.end(); ++I);
    {
        if ((*I) == overlay)
        {
            overlays.erase(I);
            return;
        }
    }
};

void LevelMap::AddEffect(StatefulUnit *effect)
{
    effects.push_back(effect);
};

void LevelMap::AddEffect(string effectName, string stateName, const Vector2df p)
{
    StatefulUnit *effect = new StatefulUnit(p, Vector2df(0.0,0.0));
    effect->loadSprite(effectName);
    effect->setSpriteState(stateName);
    if (effect)
    {
        effect->setPosition(p);
        AddEffect(effect);
    }
};

void LevelMap::RemoveEffect(StatefulUnit *effect)
{
    effects.remove(effect);
};

BaseUnit *LevelMap::SetState(string unitName, string stateName)
{
    vector< Overlay* >::iterator I;
    for (I = overlays.begin(); I < overlays.end(); ++I) {
        if ((*I)->getIdentifier() == unitName) {
#ifdef _DEBUG
    cout << "Set State " << stateName << " on Overlay " << unitName << endl;
#endif
            (*I)->setSpriteState(stateName);
            return (*I);
        }
    }
};

void LevelMap::StartSequence(string sequenceName)
{
    if (sequenceRunning) {
        sequenceStarts.push_back(sequenceName);
    } else {
        startSequenceReal(sequenceName);
    }
}

void LevelMap::SequenceDone()
{
    sequenceRunning = false;
    if (sequenceStarts.size() > 0) {
        string seq = sequenceStarts.front();
        sequenceStarts.pop_front();
        startSequenceReal(seq);
    }
}

void LevelMap::startSequenceReal(string sequenceName)
{
    vector<Sequence *>::iterator seq;

    for (seq = sequences.begin(); seq < sequences.end(); ++seq)
    {
        if ((*seq)->getName() == sequenceName) {
#ifdef _DEBUG
    cout << "Starting sequence " << sequenceName << endl;
#endif
            sequenceRunning = true;
            (*seq)->start();
        }
    }
}
